package framework.grid;
import framework.grid.data.INodeData;
import framework.grid.data.INodeMetaData;
import framework.grid.data.RemoteNodeData;
import framework.util.PeerEdge;
import framework.util.PeerNode;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.TreeMap;

import org.apache.commons.lang3.StringUtils;



public class GridAlgorithm {
	public static final String EMPTY_PATH = "";
	public static final String ROOT_PATH = "ROOT";
	public static final char BIT_LOW ='0';
	public static final char BIT_HIGH ='1';
	public static final int MAX_PATH_LENGTH = 6;
	public enum Extent {MIN, MAX}


	public static NodePeer search(String query, NodePeer peer) throws RemoteException {
		String peerPath = peer.getMetaData().getNode().getPath();
		String commonPath = StringUtils.getCommonPrefix(new String[]{query, peerPath});

		int lc = commonPath.length();
		int l2 = peerPath.length() - lc;

		//CASE1: remaining peer path is not empty, redirect search to neighbor
		if (l2>0) {			
			PeerEdge peerRef = peer.getMetaData().getRef(lc+1);

			return GridAlgorithm.search(query, peerRef.getTargetNode().getPeer());
		}
		//CASE2: remaining peer path is empty, we found our responsible peer
		else if (l2==0) {
			return peer;
		}
		//this should never be reached
		return null;
	}

	public static NodePeer findExtent(String joinerPath, NodePeer joineePeer, NodePeer lastPeer, Extent extent) throws RemoteException{
		String joineePath = joineePeer.getMetaData().getNode().getPath();
		String commonPath = StringUtils.getCommonPrefix(new String[]{joinerPath, joineePath});

		int lc = commonPath.length();
		int l1 = joinerPath.length() - lc;
		int l2 = joineePath.length() - lc;

		boolean case1=false;
		if (extent == Extent.MIN) {
			if (lastPeer!= null && joineePath.length() > lastPeer.getMetaData().getNode().getPath().length()) {
				case1=true;
			} 
		} else if (extent == Extent.MAX) {
			if (lastPeer!= null && joineePath.length() < lastPeer.getMetaData().getNode().getPath().length()) {
				case1=true;
			}
		}
		//CASE1: lastPeer has smaller(MIN)/greater(MAX) length than joineePeer
		//		 swap joinerPeers last bit and connect to lastPeer instead
		if (case1) {
			char joiner = joinerPath.charAt(lc-1);
			String newJoinerPath = joinerPath.substring(0, lc-1) + (joiner == BIT_HIGH ? BIT_LOW : BIT_HIGH);
			return GridAlgorithm.findExtent(newJoinerPath, lastPeer, joineePeer, extent);
		}
		//CASE2: joiner path is empty, extend his path and let him join with neighbor
		else if (l1==0 && l2>0 && lc<MAX_PATH_LENGTH) {
			char joinee = joineePath.charAt(lc);
			String newJoinerPath = joinerPath + (joinee == BIT_HIGH ? BIT_LOW : BIT_HIGH);			
			PeerEdge joineeRef = joineePeer.getMetaData().getRef(lc+1);

			return GridAlgorithm.findExtent(newJoinerPath, joineeRef.getTargetNode().getPeer(), joineePeer, extent);

		}
		//CASE3: both remaining paths are empty, we found our extent
		else if (l1==0 && l2==0 && lc < MAX_PATH_LENGTH) {
			return joineePeer;
		}

		//this should never be reached
		return null;
	}

	public static void join(NodePeer joinerPeer, NodePeer joineePeer) throws RemoteException {
		INodeMetaData joinerMeta = joinerPeer.getMetaData();
		INodeMetaData joineeMeta = joineePeer.getMetaData();
		String joineePath = joineeMeta.getNode().getPath();


		int lc = joineePath.length();
		if (lc < MAX_PATH_LENGTH) {
			PeerNode oldNode = joinerMeta.getNode();
			joinerMeta.setNode(new PeerNode(oldNode,joineePath+BIT_HIGH));
			PeerEdge edge = new PeerEdge(joineePath+BIT_HIGH, joinerMeta.getNode());
			joineeMeta.setRef(lc+1, edge);

			oldNode = joineeMeta.getNode();
			joineeMeta.setNode(new PeerNode(oldNode, joineePath+BIT_LOW));
			edge = new PeerEdge(joineePath+BIT_LOW, joineeMeta.getNode());
			joinerMeta.setRef(lc+1, edge);

			TreeMap<String, ArrayList<String>> lowerMap = joineePeer.getData().getValues();
			TreeMap<String, ArrayList<String>> higherMap = joinerPeer.getData().getValues();
			RemoteNodeData.splitData(lowerMap, joineePath+BIT_HIGH, higherMap);
			joineePeer.getData().setValues(lowerMap);
			joinerPeer.getData().setValues(higherMap);
		}
	}


	public static void leave (NodePeer leaverPeer) throws RemoteException{
		INodeMetaData leaverMeta = leaverPeer.getMetaData();
		int leaverLength = leaverMeta.getNode().getPath().length();


		//CASE0: leaverPeer is only node in network, length 1
		if (leaverMeta.getRef(leaverLength) == null) {
			PeerEdge ref = leaverMeta.getRef(0);
			leaverMeta.setRef(0, null);
			if (ref!=null)
				ref.getTargetNode().getPeer().getMetaData().setRef(0, null);
			
			PeerNode oldNode = leaverMeta.getNode();
			leaverMeta.setNode(new PeerNode(oldNode,EMPTY_PATH));
			return;
		}

		NodePeer partnerPeer = leaverMeta.getRef(leaverLength).getTargetNode().getPeer();
		INodeMetaData partnerMeta = partnerPeer.getMetaData();

		//remove each other's references at max level
		leaverMeta.setRef(leaverLength, null);
		partnerMeta.setRef(leaverLength, null);
		//give partnerPeer all leaverPeer's Refs and reverseRefs at lower levels
		for (int i=0; i<leaverLength; i++) {
			PeerEdge leaverRef = leaverMeta.getRef(i);
			if (leaverRef!=null) {
				//reverse Ref reroute from leaverPeer to partnerPeer
				NodePeer targetPeer = leaverRef.getTargetNode().getPeer();
				INodeMetaData targetMeta = targetPeer.getMetaData();
				String targetPath = leaverRef.getPath();
				char lastBit = targetPath.charAt(targetPath.length()-1);
				String newReversePath = 
					targetPath.substring(0, targetPath.length()-1) + (lastBit == BIT_HIGH ? BIT_LOW : BIT_HIGH);
				targetMeta.setRef(i, new PeerEdge(newReversePath, partnerMeta.getNode()));
				//add ref from leaver to partner
				partnerMeta.setRef(i, leaverRef);
			}
			leaverMeta.setRef(i, null);
		}
		//shorten partnerPeer's path to leaverPeerPath-1
		PeerNode oldNode = partnerMeta.getNode();
		partnerMeta.setNode(new PeerNode(oldNode,leaverMeta.getNode().getPath().substring(0, leaverLength-1)));

		oldNode = leaverMeta.getNode();
		leaverMeta.setNode(new PeerNode(oldNode,EMPTY_PATH));

		TreeMap<String, ArrayList<String>> lowerMap = leaverPeer.getData().getValues();
		TreeMap<String, ArrayList<String>> higherMap = partnerPeer.getData().getValues();
		RemoteNodeData.splitData(lowerMap, INodeData.ALL_TO_HIGH, higherMap);
		leaverPeer.getData().setValues(lowerMap);
		partnerPeer.getData().setValues(higherMap);
	}
}
